"""
Copyright (c) 2009, Balachandra Krishnamurthy
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name of the application, wp-tfap, nor the names of its
      contributors may be used to endorse or promote products derived from this
      software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext.webapp import template
from google.appengine.api import urlfetch
from google.appengine.api import memcache
from xml.dom import minidom
import os
import StringIO
from datetime import datetime, timedelta
import logging
import rfc3339
import rfc822formatter
from UserDict import UserDict
import traceback

class MainPage(webapp.RequestHandler):
    def get(self):
        """Process get requests.

        Accepts a feed parameter which tells which feed to get.
        Use tfa for Today's Featured Article and tfp for Today's Featured Picture.
        tfa is the default.

        Accepts a type parameter which tells which type of feed to output.
        Use atom for atom feeds and rss2 for rss2.0 feeds.
        atom is the default.
        """
        feed = self.request.get('feed')
        type = self.request.get('type')

        if feed == '' and type == '':
            # render main page
            f = open('index.html')
            self.response.headers['Content-Type'] = 'text/html'
            self.response.out.write(f.read())
            f.close()
            return

        if feed != '':
            if feed != 'tfa' and feed != 'tfp':
                # feed is not blank and is not equal to tfa or tfp
                self.error(400)
                self.response.out.write('feed has to be tfa or tfp')
                return
        else:
            # feed is blank
            feed = 'tfa'

        type = self.request.get('type')
        if type != '':
            if type != 'atom' and type != 'rss2':
                # type is not blank and is not equal to atom or rss2
                self.error(400)
                self.response.out.write('type has to be atom or rss2')
                return
        else:
            type = 'atom'

        # generate feed
        genObj = FeedGen()
        feedParams = genObj.gen(feed)
        if type == 'atom':
            feedParams['updated'] = rfc3339.rfc3339(feedParams['updated'])
        else:
            feedParams['updated'] = rfc822formatter.timeAsrfc822(feedParams['updated'])
        if type == 'atom':
            path = os.path.join(os.path.dirname(__file__), 'atom.xml')
        else:
            path = os.path.join(os.path.dirname(__file__), 'rss2.xml')
        # send response
        self.response.headers['Content-Type'] = 'application/xml'
        self.response.out.write(template.render(path, feedParams))

class FeedGen():
    """Class to generate feeds."""
    def gen(self, feedname='tfa'):
        """Generate a feed. The method first looks up the data store to check if the feed is available.
        If so, it simply returns it. Otherwise, it generates a new feed by downloading and parsing
        the wikipedia main page. New feeds are also generated if more than a day has elapsed since the
        the last time the feed was generated.
        """
        feed = memcache.get(feedname)
        if feed is None:
            # either no feeds were found or the feed is more than a day old.
            logging.info("Need to generate a new feed")
            feed = self.gen_new_feed(feedname)
            memcache.add(feedname, feed, 86400)
            return feed
        else:
            # found a suitable feed. Return it
            logging.info("no need to generate a new feed")
            return feed

    def gen_new_feed(self, feedname):
        """Wrapper method that calls the appropriate new_feed method."""
        if feedname == 'tfp':
            return self.gen_new_tfp_feed()
        else:
            return self.gen_new_tfa_feed()

    def gen_new_tfa_feed(self):
        """Generates a new featured article feed by downloading wikipedia's main page and parsing it."""
        logging.info("fetching wikipedia main page")
        ret = {}
        url = "http://en.wikipedia.org/wiki/Main_Page"
        try:
            result = urlfetch.fetch(url)
        except Exception:
            logging.error(traceback.format_exc())
            return self.getErrorFeed('tfa')
        if 200 == result.status_code:
            try:
                dom = minidom.parseString(result.content)
                non_converted_div = self.getElementById(dom.getElementsByTagName('div'), 'mp-tfa')
                div = self.convertlinks(non_converted_div)
                aLink = div.getElementsByTagName('a')[1]
                url = aLink.attributes.getNamedItem('href').value
                ret['url'] = url
                title_key, title = aLink.attributes.items()[1]
                ret['title'] = title
                ret['summary'] = self.getTfaSummary(div.parentNode)
                ret['updated'] = datetime.now()
                ret['id'] = url
                ret['feed'] = 'tfa'
                return ret
            except Exception:
                logging.error(traceback.format_exc())
                return self.getErrorFeed('tfa')
        else:
            logging.error('HTTP Error ' + `result.status_code` + ' while trying to fetch main page')
            return self.getErrorFeed('tfa')

    def gen_new_tfp_feed(self):
        """Generates a new featured picture feed by downloading wikipedia's main page and parsing it."""
        logging.info("fetching wikipedia main page")
        ret = {}
        url = "http://en.wikipedia.org/wiki/Main_Page"
        try:
            result = urlfetch.fetch(url)
        except Exception:
            logging.error(traceback.format_exc())
            return self.getErrorFeed('tfp')
        if 200 == result.status_code:
            try:
                dom = minidom.parseString(result.content)
                table = self.getElementById(dom.getElementsByTagName('table'), 'mp-tfp')
                realtable = table.getElementsByTagName('table')[1]
                non_converted_tr = realtable.childNodes[1]
                tr = self.convertlinks(non_converted_tr)
                anodes = tr.getElementsByTagName('a')
                url = anodes[0].attributes.getNamedItem('href').value
                ret['url'] = url
                title = anodes[0].attributes.getNamedItem('title').value
                ret['title'] = title
                ret['summary'] = self.getTfpSummary(realtable)
                ret['updated'] = datetime.now()
                ret['id'] = url
                ret['feed'] = 'tfp'
                return ret
            except Exception:
                logging.error(traceback.format_exc())
                return self.getErrorFeed('tfp')
        else:
            logging.error('HTTP Error ' + `result.status_code` + ' while trying to fetch main page')
            return self.getErrorFeed('tfp')

    def getElementById(self, nodes, id):
        """Without proper doctype headings or schema, the dom.getElementById in the dom doesn't work."""
        for node in nodes:
            for item in node.attributes.items():
                a, b = item
                if a == 'id' and b == id:
                    return node
        return None

    def getTfaSummary(self, div):
        """Parse the div node that contains the short description for the featured article and return the short description."""
        summary = StringIO.StringIO()
        for n in div.childNodes:
            summary.write(n.toxml())
        return summary.getvalue()

    def getTfpSummary(self, tr):
        """Parse the tr node that contains the short description for the featured picture and return the short description."""
        summary = StringIO.StringIO()
        summary.write(tr.toxml())
        return summary.getvalue()

    def getErrorFeed(self, feed, message = None):
        """Method to return a feed with an error message"""
        default_message = 'Something bad happened while generating the feed. The developer in question will be fired immediately after he fixes the bug. In the mean time, click on the title to visit Wikipedia.'
        ret = {}
        url = 'http://en.wikipedia.org/wiki/Main_Page'
        ret['url'] = url
        ret['title'] = 'Wikipedia Featured Article/Picture Feed Generator'
        ret['summary'] = message if message is not None else default_message
        ret['updated'] = datetime.now()
        ret['id'] = url
        ret['feed'] = feed
        return ret

    def convertlinks(self, div):
        """Prepends http://en.wikipedia.org to all hrefs if they don't start with it already."""
        aLinks = div.getElementsByTagName('a')
        for aLink in aLinks:
            link = aLink.attributes.getNamedItem('href').value
            if not link.startswith('http://en.wikipedia.org'):
                aLink.attributes.getNamedItem('href').value = "".join(['http://en.wikipedia.org', link])
        return div

application = webapp.WSGIApplication([('/', MainPage)], debug=True)

def main():
    run_wsgi_app(application)

if __name__=="__main__":
    main()

